package it.gtd.swimpool.model.business;

import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.mysema.query.jpa.impl.JPAQuery;
import it.gtd.swimpool.model.entity.Administrator;
import it.gtd.swimpool.model.entity.QSkill;
import it.gtd.swimpool.model.entity.Skill;
import it.gtd.swimpool.model.entity.User;

import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import java.util.LinkedList;
import java.util.List;

import static com.google.common.base.Preconditions.*;

@Stateless
@LocalBean
@Beta
public class SkillManager {
    private static final String EMAIL_PROPOSAL_SUBJECT = "A skill proposal of yours has been %s!";
    private static final String EMAIL_PROPOSAL_BODY = "The skill %s you proposed has been %s by %s.";

    @PersistenceContext
    private EntityManager entityManager;

    @EJB
    private CommunicationManager communicationManager;

    public SkillManager() {
    }

    @VisibleForTesting
    SkillManager(EntityManager entityManager, CommunicationManager communicationManager) {
        checkNotNull(entityManager, "entityManager can't be null");
        checkNotNull(communicationManager, "communicationManager can't be null");

        this.entityManager = entityManager;
        this.communicationManager = communicationManager;
    }

    public void add(User user, Skill skill) {
        checkNotNull(user, "user can't be null");
        checkNotNull(skill, "skill can't be null");
        checkArgument(entityManager.contains(user), "User " + user.getEmail() + "is not managed by entityManager");
        checkArgument(entityManager.contains(skill), "Skill " + skill.getName() + "is not managed by entityManager");
        checkArgument(!user.getSkills().contains(skill), "User " + user.getEmail() + " already possesses skill " + skill.getName());

        user.addSkill(skill);

        checkState(user.getSkills().contains(skill));
    }

    public void remove(User user, Skill skill) {
        checkNotNull(user, "user can't be null");
        checkNotNull(skill, "skill can't be null");
        checkState(user.getSkills().contains(skill));
        checkArgument(entityManager.contains(user), "User " + user.getEmail() + "is not managed by entityManager");
        checkArgument(entityManager.contains(skill), "Skill " + skill.getName() + "is not managed by entityManager");

        user.removeSkill(skill);

        checkState(!user.getSkills().contains(skill));
    }

    public void propose(User proposer, Skill skill) {
        checkNotNull(proposer, "proposer can't be null");
        checkNotNull(skill, "skill can't be null");
        checkArgument(entityManager.contains(proposer), "User " + proposer.getEmail() + "is not managed by entityManager");
        checkArgument(!entityManager.contains(skill), "Skill " + skill.getName() + "is alredy managed by entityManager");

        skill.setName(skill.getName().trim());

        QSkill skillModel = QSkill.skill;
        JPAQuery query = new JPAQuery(entityManager);

        query.from(skillModel).where(skillModel.name.equalsIgnoreCase(skill.getName()));

        checkArgument(!query.exists(), "A skill with that name already exists");

        skill.setProposer(proposer);
        entityManager.persist(skill);

        checkState(skill.getProposer().equals(proposer));
        checkState(entityManager.contains(skill), "Skill " + skill.getName() + "is not managed by entityManager");
    }

    public void approve(Administrator approver, Skill skill) {
        checkNotNull(approver, "approver can't be null");
        checkNotNull(skill, "skill can't be null");
        checkArgument(entityManager.contains(approver), "User " + approver.getEmail() + "is not managed by entityManager");
        checkArgument(entityManager.contains(skill), "Skill " + skill.getName() + "is not managed by entityManager");
        checkArgument(skill.getApprover() == null, "The skill " + skill.getName() + " has already been approved.");

        skill.setApprover(approver);
        skill.getProposer().addSkill(skill);

        communicationManager.sendEmail(
            skill.getProposer(),
            String.format(EMAIL_PROPOSAL_SUBJECT, "approved"),
            String.format(EMAIL_PROPOSAL_BODY, skill.getName(), "approved", approver.getEmail()));

        checkState(skill.getProposer().getSkills().contains(skill), "Skill " + skill.getName() + " is not contained by " + skill.getProposer().getName() + " skillset");
        checkState(skill.getApprover().equals(approver));
    }

    public void decline(Administrator approver, Skill skill) {
        checkNotNull(approver, "approver can't be null");
        checkNotNull(skill, "skill can't be null");
        checkArgument(entityManager.contains(approver), "User " + approver.getEmail() + "is not managed by entityManager");
        checkArgument(entityManager.contains(skill), "Skill " + skill.getName() + "is not managed by entityManager");
        checkArgument(skill.getApprover() == null, "The skill " + skill.getName() + " has already been approved.");

        communicationManager.sendEmail(
            skill.getProposer(),
            String.format(EMAIL_PROPOSAL_SUBJECT, "rejected"),
            String.format(EMAIL_PROPOSAL_BODY, skill.getName(), "rejected", approver.getEmail()));

        entityManager.remove(skill);

        checkState(!entityManager.contains(skill), "Skill is contained in entityManager");
    }

    public void declare(Administrator declarer, Skill skill) {
        checkNotNull(declarer, "declarer can't be null");
        checkNotNull(skill, "skill can't be null");
        checkArgument(!entityManager.contains(skill), "Skill " + skill.getName() + " is alredy managed!");
        checkArgument(entityManager.contains(declarer), "Administrator " + declarer.getName() + " is not a managed entity");

        skill.setProposer(declarer);
        skill.setApprover(declarer);
        entityManager.persist(skill);

        checkState(entityManager.contains(skill), "Skill " + skill.getName() + " is not managed!");
        checkState(skill.getApprover().equals(declarer));
        checkState(skill.getProposer().equals(declarer));
    }

    public List<Skill> getProposals() {
        try {
            return entityManager.createNamedQuery("Skill.proposals", Skill.class).getResultList();
        } catch (NoResultException _) {
            return new LinkedList<Skill>();
        }
    }
}
